/*
 * File    : DisplayFormatters.java
 * Created : 07-Oct-2003
 * By      : gardnerpar
 *
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.core3.util;

/**
 * @author gardnerpar
 *
 */

import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;

import org.gudy.azureus2.core3.config.COConfigurationListener;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerStats;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.torrent.TOTorrent;

public class DisplayFormatters {
    final private static boolean ROUND_NO = true;
    // final private static boolean ROUND_YES = false;
    final private static boolean TRUNCZEROS_NO = false;
    final private static boolean TRUNCZEROS_YES = true;

    final public static int UNIT_B = 0;
    final public static int UNIT_KB = 1;
    final public static int UNIT_MB = 2;
    final public static int UNIT_GB = 3;
    final public static int UNIT_TB = 4;

    final private static int UNITS_PRECISION[] = { 0, // B
            1, // KB
            2, // MB
            2, // GB
            3 // TB
            };

    final private static NumberFormat[] cached_number_formats = new NumberFormat[20];

    private static NumberFormat percentage_format;

    private static String[] units;
    private static String[] units_bits;
    private static String[] units_rate;
    private static int unitsStopAt = UNIT_TB;

    private static String[] units_base10;

    private static String per_sec;

    private static boolean use_si_units;
    private static boolean force_si_values;
    private static boolean use_units_rate_bits;
    private static boolean not_use_GB_TB;

    private static int message_text_state = 0;

    private static boolean separate_prot_data_stats;
    private static boolean data_stats_only;
    private static char decimalSeparator;

    static {
        COConfigurationManager.addAndFireParameterListeners(new String[] { "config.style.useSIUnits", "config.style.forceSIValues",
                "config.style.useUnitsRateBits", "config.style.doNotUseGB", }, new ParameterListener() {
            public void parameterChanged(String x) {
                use_si_units = COConfigurationManager.getBooleanParameter("config.style.useSIUnits");
                force_si_values = COConfigurationManager.getBooleanParameter("config.style.forceSIValues");
                use_units_rate_bits = COConfigurationManager.getBooleanParameter("config.style.useUnitsRateBits");
                not_use_GB_TB = COConfigurationManager.getBooleanParameter("config.style.doNotUseGB");

                unitsStopAt = (not_use_GB_TB) ? UNIT_MB : UNIT_TB;

                setUnits();
            }
        });

        COConfigurationManager.addListener(new COConfigurationListener() {
            public void configurationSaved() {
                setUnits();
                loadMessages();

            }

        });

        COConfigurationManager.addAndFireParameterListeners(new String[] { "config.style.dataStatsOnly", "config.style.separateProtDataStats" },
                new ParameterListener() {
                    public void parameterChanged(String x) {
                        separate_prot_data_stats = COConfigurationManager.getBooleanParameter("config.style.separateProtDataStats");
                        data_stats_only = COConfigurationManager.getBooleanParameter("config.style.dataStatsOnly");
                    }
                });

        setUnits();

        loadMessages();
    }

    public static void setUnits() {
        // (1) http://physics.nist.gov/cuu/Units/binary.html
        // (2) http://www.isi.edu/isd/LOOM/documentation/unit-definitions.text

        units = new String[unitsStopAt + 1];
        units_bits = new String[unitsStopAt + 1];
        units_rate = new String[unitsStopAt + 1];

        if (use_si_units) {
            // fall through intentional
            switch (unitsStopAt) {
                case UNIT_TB:
                    units[UNIT_TB] = getUnit("TiB");
                    units_bits[UNIT_TB] = getUnit("Tibit");
                    units_rate[UNIT_TB] = (use_units_rate_bits) ? getUnit("Tibit") : getUnit("TiB");
                case UNIT_GB:
                    units[UNIT_GB] = getUnit("GiB");
                    units_bits[UNIT_GB] = getUnit("Gibit");
                    units_rate[UNIT_GB] = (use_units_rate_bits) ? getUnit("Gibit") : getUnit("GiB");
                case UNIT_MB:
                    units[UNIT_MB] = getUnit("MiB");
                    units_bits[UNIT_MB] = getUnit("Mibit");
                    units_rate[UNIT_MB] = (use_units_rate_bits) ? getUnit("Mibit") : getUnit("MiB");
                case UNIT_KB:
                    // can be upper or lower case k
                    units[UNIT_KB] = getUnit("KiB");
                    units_bits[UNIT_KB] = getUnit("Kibit");

                    // can be upper or lower case k, upper more consistent
                    units_rate[UNIT_KB] = (use_units_rate_bits) ? getUnit("Kibit") : getUnit("KiB");
                case UNIT_B:
                    units[UNIT_B] = getUnit("B");
                    units_bits[UNIT_B] = getUnit("bit");
                    units_rate[UNIT_B] = (use_units_rate_bits) ? getUnit("bit") : getUnit("B");
            }
        } else {
            switch (unitsStopAt) {
                case UNIT_TB:
                    units[UNIT_TB] = getUnit("TB");
                    units_bits[UNIT_TB] = getUnit("Tbit");
                    units_rate[UNIT_TB] = (use_units_rate_bits) ? getUnit("Tbit") : getUnit("TB");
                case UNIT_GB:
                    units[UNIT_GB] = getUnit("GB");
                    units_bits[UNIT_GB] = getUnit("Gbit");
                    units_rate[UNIT_GB] = (use_units_rate_bits) ? getUnit("Gbit") : getUnit("GB");
                case UNIT_MB:
                    units[UNIT_MB] = getUnit("MB");
                    units_bits[UNIT_MB] = getUnit("Mbit");
                    units_rate[UNIT_MB] = (use_units_rate_bits) ? getUnit("Mbit") : getUnit("MB");
                case UNIT_KB:
                    // yes, the k should be lower case
                    units[UNIT_KB] = getUnit("kB");
                    units_bits[UNIT_KB] = getUnit("kbit");
                    units_rate[UNIT_KB] = (use_units_rate_bits) ? getUnit("kbit") : getUnit("kB");
                case UNIT_B:
                    units[UNIT_B] = getUnit("B");
                    units_bits[UNIT_B] = getUnit("bit");
                    units_rate[UNIT_B] = (use_units_rate_bits) ? getUnit("bit") : getUnit("B");
            }
        }

        per_sec = getResourceString("Formats.units.persec", "/s");

        units_base10 =
                new String[] { getUnit(use_units_rate_bits ? "bit" : "B"), getUnit(use_units_rate_bits ? "kbit" : "KB"),
                        getUnit(use_units_rate_bits ? "Mbit" : "MB"), getUnit(use_units_rate_bits ? "Gbit" : "GB"),
                        getUnit(use_units_rate_bits ? "Tbit" : "TB") };

        for (int i = 0; i <= unitsStopAt; i++) {
            units[i] = units[i];
            units_rate[i] = units_rate[i] + per_sec;
        }

        Arrays.fill(cached_number_formats, null);

        percentage_format = NumberFormat.getPercentInstance();
        percentage_format.setMinimumFractionDigits(1);
        percentage_format.setMaximumFractionDigits(1);

        decimalSeparator = new DecimalFormatSymbols().getDecimalSeparator();
    }

    private static String getUnit(String key) {
        String res = " " + getResourceString("Formats.units." + key, key);

        return (res);
    }

    private static String PeerManager_status_finished;
    private static String PeerManager_status_finishedin;
    private static String Formats_units_alot;
    private static String discarded;
    private static String ManagerItem_waiting;
    private static String ManagerItem_initializing;
    private static String ManagerItem_allocating;
    private static String ManagerItem_checking;
    private static String ManagerItem_finishing;
    private static String ManagerItem_ready;
    private static String ManagerItem_downloading;
    private static String ManagerItem_seeding;
    private static String ManagerItem_superseeding;
    private static String ManagerItem_stopping;
    private static String ManagerItem_stopped;
    private static String ManagerItem_paused;
    private static String ManagerItem_queued;
    private static String ManagerItem_error;
    private static String ManagerItem_forced;
    private static String ManagerItem_moving;

    private static String yes;
    private static String no;

    public static void loadMessages() {
        PeerManager_status_finished = getResourceString("PeerManager.status.finished", "Finished");
        PeerManager_status_finishedin = getResourceString("PeerManager.status.finishedin", "Finished in");
        Formats_units_alot = getResourceString("Formats.units.alot", "A lot");
        discarded = getResourceString("discarded", "discarded");
        ManagerItem_waiting = getResourceString("ManagerItem.waiting", "waiting");
        ManagerItem_initializing = getResourceString("ManagerItem.initializing", "initializing");
        ManagerItem_allocating = getResourceString("ManagerItem.allocating", "allocating");
        ManagerItem_checking = getResourceString("ManagerItem.checking", "checking");
        ManagerItem_finishing = getResourceString("ManagerItem.finishing", "finishing");
        ManagerItem_ready = getResourceString("ManagerItem.ready", "ready");
        ManagerItem_downloading = getResourceString("ManagerItem.downloading", "downloading");
        ManagerItem_seeding = getResourceString("ManagerItem.seeding", "seeding");
        ManagerItem_superseeding = getResourceString("ManagerItem.superseeding", "superseeding");
        ManagerItem_stopping = getResourceString("ManagerItem.stopping", "stopping");
        ManagerItem_stopped = getResourceString("ManagerItem.stopped", "stopped");
        ManagerItem_paused = getResourceString("ManagerItem.paused", "paused");
        ManagerItem_queued = getResourceString("ManagerItem.queued", "queued");
        ManagerItem_error = getResourceString("ManagerItem.error", "error");
        ManagerItem_forced = getResourceString("ManagerItem.forced", "forced");
        ManagerItem_moving = getResourceString("ManagerItem.moving", "moving");
        yes = getResourceString("GeneralView.yes", "Yes");
        no = getResourceString("GeneralView.no", "No");
    }

    private static String getResourceString(String key, String def) {
        if (message_text_state == 0) {

            // this fooling around is to permit the use of this class in the absence of the (large) overhead
            // of resource bundles

            try {
                MessageText.class.getName();

                message_text_state = 1;

            } catch (Throwable e) {

                message_text_state = 2;
            }
        }

        if (message_text_state == 1) {

            return (MessageText.getString(key));

        } else {

            return (def);
        }
    }

    public static String getYesNo(boolean b) {
        return (b ? yes : no);
    }

    public static String getRateUnit(int unit_size) {
        return (units_rate[unit_size].substring(1, units_rate[unit_size].length()));
    }

    public static String getUnit(int unit_size) {
        return (units[unit_size].substring(1, units[unit_size].length()));
    }

    public static String getRateUnitBase10(int unit_size) {
        return units_base10[unit_size] + per_sec;
    }

    public static String getUnitBase10(int unit_size) {
        return units_base10[unit_size];
    }

    public static boolean isRateUsingBits() {
        return (use_units_rate_bits);
    }

    public static String formatByteCountToKiBEtc(int n) {
        return (formatByteCountToKiBEtc((long) n));
    }

    public static String formatByteCountToKiBEtc(long n) {
        return (formatByteCountToKiBEtc(n, false, TRUNCZEROS_NO));
    }

    public static String formatByteCountToKiBEtc(long n, boolean bTruncateZeros) {
        return (formatByteCountToKiBEtc(n, false, bTruncateZeros));
    }

    public static String formatByteCountToKiBEtc(long n, boolean rate, boolean bTruncateZeros) {
        return formatByteCountToKiBEtc(n, rate, bTruncateZeros, -1);
    }

    public static String formatByteCountToKiBEtc(long n, boolean rate, boolean bTruncateZeros, int precision) {
        double dbl = (rate && use_units_rate_bits) ? n * 8 : n;

        int unitIndex = UNIT_B;

        long div = force_si_values ? 1024 : (use_si_units ? 1024 : 1000);

        while (dbl >= div && unitIndex < unitsStopAt) {

            dbl /= div;
            unitIndex++;
        }

        if (precision < 0) {
            precision = UNITS_PRECISION[unitIndex];
        }

        // round for rating, because when the user enters something like 7.3kbps
        // they don't want it truncated and displayed as 7.2
        // (7.3*1024 = 7475.2; 7475/1024.0 = 7.2998; trunc(7.2998, 1 prec.) == 7.2
        //
        // Truncate for rest, otherwise we get complaints like:
        // "I have a 1.0GB torrent and it says I've downloaded 1.0GB.. why isn't
        // it complete? waaah"

        return formatDecimal(dbl, precision, bTruncateZeros, rate) + (rate ? units_rate[unitIndex] : units[unitIndex]);
    }

    public static String formatByteCountToKiBEtc(long n, boolean rate, boolean bTruncateZeros, int precision, int minUnit) {
        double dbl = (rate && use_units_rate_bits) ? n * 8 : n;

        int unitIndex = UNIT_B;

        long div = force_si_values ? 1024 : (use_si_units ? 1024 : 1000);

        while (dbl >= div && unitIndex < unitsStopAt) {

            dbl /= div;
            unitIndex++;
        }

        while (unitIndex < minUnit) {
            dbl /= div;
            unitIndex++;
        }
        if (precision < 0) {
            precision = UNITS_PRECISION[unitIndex];
        }

        // round for rating, because when the user enters something like 7.3kbps
        // they don't want it truncated and displayed as 7.2
        // (7.3*1024 = 7475.2; 7475/1024.0 = 7.2998; trunc(7.2998, 1 prec.) == 7.2
        //
        // Truncate for rest, otherwise we get complaints like:
        // "I have a 1.0GB torrent and it says I've downloaded 1.0GB.. why isn't
        // it complete? waaah"

        return formatDecimal(dbl, precision, bTruncateZeros, rate) + (rate ? units_rate[unitIndex] : units[unitIndex]);
    }

    public static boolean isDataProtSeparate() {
        return (separate_prot_data_stats);
    }

    public static String formatDataProtByteCountToKiBEtc(long data, long prot) {
        if (separate_prot_data_stats) {
            if (data == 0 && prot == 0) {
                return (formatByteCountToKiBEtc(0));
            } else if (data == 0) {
                return ("(" + formatByteCountToKiBEtc(prot) + ")");
            } else if (prot == 0) {
                return (formatByteCountToKiBEtc(data));
            } else {
                return (formatByteCountToKiBEtc(data) + " (" + formatByteCountToKiBEtc(prot) + ")");
            }
        } else if (data_stats_only) {
            return (formatByteCountToKiBEtc(data));
        } else {
            return (formatByteCountToKiBEtc(prot + data));
        }
    }

    public static String formatDataProtByteCountToKiBEtcPerSec(long data, long prot) {
        if (separate_prot_data_stats) {
            if (data == 0 && prot == 0) {
                return (formatByteCountToKiBEtcPerSec(0));
            } else if (data == 0) {
                return ("(" + formatByteCountToKiBEtcPerSec(prot) + ")");
            } else if (prot == 0) {
                return (formatByteCountToKiBEtcPerSec(data));
            } else {
                return (formatByteCountToKiBEtcPerSec(data) + " (" + formatByteCountToKiBEtcPerSec(prot) + ")");
            }
        } else if (data_stats_only) {
            return (formatByteCountToKiBEtcPerSec(data));
        } else {
            return (formatByteCountToKiBEtcPerSec(prot + data));
        }
    }

    public static String formatByteCountToKiBEtcPerSec(long n) {
        return (formatByteCountToKiBEtc(n, true, TRUNCZEROS_NO));
    }

    public static String formatByteCountToKiBEtcPerSec(long n, boolean bTruncateZeros) {
        return (formatByteCountToKiBEtc(n, true, bTruncateZeros));
    }

    // base 10 ones

    public static String formatByteCountToBase10KBEtc(long n) {
        if (use_units_rate_bits) {
            n *= 8;
        }

        if (n < 1000) {

            return n + units_base10[UNIT_B];

        } else if (n < 1000 * 1000) {

            return (n / 1000) + "." + ((n % 1000) / 100) + units_base10[UNIT_KB];

        } else if (n < 1000L * 1000L * 1000L || not_use_GB_TB) {

            return (n / (1000L * 1000L)) + "." + ((n % (1000L * 1000L)) / (1000L * 100L)) + units_base10[UNIT_MB];

        } else if (n < 1000L * 1000L * 1000L * 1000L) {

            return (n / (1000L * 1000L * 1000L)) + "." + ((n % (1000L * 1000L * 1000L)) / (1000L * 1000L * 100L)) + units_base10[UNIT_GB];

        } else if (n < 1000L * 1000L * 1000L * 1000L * 1000L) {

            return (n / (1000L * 1000L * 1000L * 1000L)) + "." + ((n % (1000L * 1000L * 1000L * 1000L)) / (1000L * 1000L * 1000L * 100L))
                    + units_base10[UNIT_TB];
        } else {

            return Formats_units_alot;
        }
    }

    public static String formatByteCountToBase10KBEtcPerSec(long n) {
        return (formatByteCountToBase10KBEtc(n) + per_sec);
    }

    /**
     * Print the BITS/second in an international format.
     * 
     * @param n
     *            - always formatted using SI (i.e. decimal) prefixes
     * @return String in an internationalized format.
     */
    public static String formatByteCountToBitsPerSec(long n) {
        double dbl = n * 8;

        int unitIndex = UNIT_B;

        long div = 1000;

        while (dbl >= div && unitIndex < unitsStopAt) {

            dbl /= div;
            unitIndex++;
        }

        int precision = UNITS_PRECISION[unitIndex];

        return (formatDecimal(dbl, precision, true, true) + units_bits[unitIndex] + per_sec);
    }

    public static String formatETA(long eta) {
        return (formatETA(eta, false));
    }

    private static final SimpleDateFormat abs_df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    public static String formatETA(long eta, boolean abs) {
        if (eta == 0)
            return PeerManager_status_finished;
        if (eta == -1)
            return "";
        if (eta > 0) {
            if (abs && !(eta == Constants.CRAPPY_INFINITY_AS_INT || eta >= Constants.CRAPPY_INFINITE_AS_LONG)) {

                long now = SystemTime.getCurrentTime();
                long then = now + eta * 1000;

                if (eta > 5 * 60) {

                    then = (then / (60 * 1000)) * (60 * 1000);
                }

                String str1;
                String str2;

                synchronized (abs_df) {
                    str1 = abs_df.format(new Date(now));
                    str2 = abs_df.format(new Date(then));
                }

                int len = Math.min(str1.length(), str2.length()) - 2;

                int diff_at = len;

                for (int i = 0; i < len; i++) {

                    char c1 = str1.charAt(i);

                    if (c1 != str2.charAt(i)) {

                        diff_at = i;

                        break;
                    }
                }

                String res;

                if (diff_at >= 11) {

                    res = str2.substring(11);

                } else if (diff_at >= 5) {

                    res = str2.substring(5);

                } else {

                    res = str2;
                }

                return (res);

            } else {
                return TimeFormatter.format(eta);
            }
        }

        return PeerManager_status_finishedin + " " + TimeFormatter.format(eta * -1);
    }

    public static String formatDownloaded(DownloadManagerStats stats) {
        long total_discarded = stats.getDiscarded();
        long total_received = stats.getTotalGoodDataBytesReceived();

        if (total_discarded == 0) {

            return formatByteCountToKiBEtc(total_received);

        } else {

            return formatByteCountToKiBEtc(total_received) + " ( " + DisplayFormatters.formatByteCountToKiBEtc(total_discarded) + " " + discarded
                    + " )";
        }
    }

    public static String formatHashFails(DownloadManager download_manager) {
        TOTorrent torrent = download_manager.getTorrent();

        if (torrent != null) {

            long bad = download_manager.getStats().getHashFailBytes();

            // size can exceed int so ensure longs used in multiplication

            long count = bad / (long) torrent.getPieceLength();

            String result = count + " ( " + formatByteCountToKiBEtc(bad) + " )";

            return result;
        }

        return "";
    }

    public static String formatDownloadStatus(DownloadManager manager) {
        if (manager == null) {

            return (ManagerItem_error + ": Download is null");
        }

        int state = manager.getState();

        String tmp = "";

        switch (state) {
            case DownloadManager.STATE_QUEUED:
                tmp = ManagerItem_queued;
                break;

            case DownloadManager.STATE_DOWNLOADING:
                tmp = ManagerItem_downloading;
                break;

            case DownloadManager.STATE_SEEDING: {

                DiskManager diskManager = manager.getDiskManager();

                if (diskManager != null) {

                    int mp = diskManager.getMoveProgress();

                    if (mp != -1) {

                        tmp = ManagerItem_moving + ": " + formatPercentFromThousands(mp);

                    } else {
                        int done = diskManager.getCompleteRecheckStatus();

                        if (done != -1) {

                            tmp = ManagerItem_seeding + " + " + ManagerItem_checking + ": " + formatPercentFromThousands(done);
                        }
                    }
                }

                if (tmp == "") {

                    if (manager.getPeerManager() != null && manager.getPeerManager().isSuperSeedMode()) {

                        tmp = ManagerItem_superseeding;

                    } else {

                        tmp = ManagerItem_seeding;
                    }
                }

                break;
            }
            case DownloadManager.STATE_STOPPED:
                tmp = manager.isPaused() ? ManagerItem_paused : ManagerItem_stopped;
                break;

            case DownloadManager.STATE_ERROR:
                tmp = ManagerItem_error + ": " + manager.getErrorDetails();
                break;

            case DownloadManager.STATE_WAITING:
                tmp = ManagerItem_waiting;
                break;

            case DownloadManager.STATE_INITIALIZING:
                tmp = ManagerItem_initializing;
                break;

            case DownloadManager.STATE_INITIALIZED:
                tmp = ManagerItem_initializing;
                break;

            case DownloadManager.STATE_ALLOCATING: {
                tmp = ManagerItem_allocating;
                DiskManager diskManager = manager.getDiskManager();
                if (diskManager != null) {
                    tmp += ": " + formatPercentFromThousands(diskManager.getPercentDone());
                }
                break;
            }
            case DownloadManager.STATE_CHECKING:
                tmp = ManagerItem_checking + ": " + formatPercentFromThousands(manager.getStats().getCompleted());
                break;

            case DownloadManager.STATE_FINISHING:
                tmp = ManagerItem_finishing;
                break;

            case DownloadManager.STATE_READY:
                tmp = ManagerItem_ready;
                break;

            case DownloadManager.STATE_STOPPING:
                tmp = ManagerItem_stopping;
                break;

            default:
                tmp = String.valueOf(state);
        }

        if (manager.isForceStart() && (state == DownloadManager.STATE_SEEDING || state == DownloadManager.STATE_DOWNLOADING))
            tmp = ManagerItem_forced + " " + tmp;
        return (tmp);
    }

    public static String formatDownloadStatusDefaultLocale(DownloadManager manager) {
        int state = manager.getState();

        String tmp = "";

        DiskManager dm = manager.getDiskManager();

        switch (state) {
            case DownloadManager.STATE_WAITING:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.waiting");
                break;
            case DownloadManager.STATE_INITIALIZING:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.initializing");
                break;
            case DownloadManager.STATE_INITIALIZED:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.initializing");
                break;
            case DownloadManager.STATE_ALLOCATING:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.allocating");
                break;
            case DownloadManager.STATE_CHECKING:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.checking");
                break;
            case DownloadManager.STATE_FINISHING:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.finishing");
                break;
            case DownloadManager.STATE_READY:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.ready");
                break;
            case DownloadManager.STATE_DOWNLOADING:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.downloading");
                break;
            case DownloadManager.STATE_SEEDING:
                if (dm != null && dm.getCompleteRecheckStatus() != -1) {
                    int done = dm.getCompleteRecheckStatus();

                    if (done == -1) {
                        done = 1000;
                    }

                    tmp =
                            MessageText.getDefaultLocaleString("ManagerItem.seeding") + " + "
                                    + MessageText.getDefaultLocaleString("ManagerItem.checking") + ": " + formatPercentFromThousands(done);
                } else if (manager.getPeerManager() != null && manager.getPeerManager().isSuperSeedMode()) {

                    tmp = MessageText.getDefaultLocaleString("ManagerItem.superseeding");
                } else {
                    tmp = MessageText.getDefaultLocaleString("ManagerItem.seeding");
                }
                break;
            case DownloadManager.STATE_STOPPING:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.stopping");
                break;
            case DownloadManager.STATE_STOPPED:
                tmp = MessageText.getDefaultLocaleString(manager.isPaused() ? "ManagerItem.paused" : "ManagerItem.stopped");
                break;
            case DownloadManager.STATE_QUEUED:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.queued");
                break;
            case DownloadManager.STATE_ERROR:
                tmp = MessageText.getDefaultLocaleString("ManagerItem.error").concat(": ").concat(manager.getErrorDetails()); //$NON-NLS-1$ //$NON-NLS-2$
                break;
            default:
                tmp = String.valueOf(state);
        }

        return (tmp);
    }

    public static String trimDigits(String str, int num_digits) {
        char[] chars = str.toCharArray();
        String res = "";
        int digits = 0;

        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];
            if (Character.isDigit(c)) {
                digits++;
                if (digits <= num_digits) {
                    res += c;
                }
            } else if (c == '.' && digits >= 3) {

            } else {
                res += c;
            }
        }

        return (res);
    }

    public static String formatPercentFromThousands(int thousands) {

        return percentage_format.format(thousands / 1000.0);
    }

    public static String formatTimeStamp(long time) {
        StringBuffer sb = new StringBuffer();
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(time);
        sb.append('[');
        sb.append(formatIntToTwoDigits(calendar.get(Calendar.DAY_OF_MONTH)));
        sb.append('.');
        sb.append(formatIntToTwoDigits(calendar.get(Calendar.MONTH) + 1)); // 0 based
        sb.append('.');
        sb.append(calendar.get(Calendar.YEAR));
        sb.append(' ');
        sb.append(formatIntToTwoDigits(calendar.get(Calendar.HOUR_OF_DAY)));
        sb.append(':');
        sb.append(formatIntToTwoDigits(calendar.get(Calendar.MINUTE)));
        sb.append(':');
        sb.append(formatIntToTwoDigits(calendar.get(Calendar.SECOND)));
        sb.append(']');
        return sb.toString();
    }

    public static String formatIntToTwoDigits(int n) {
        return n < 10 ? "0".concat(String.valueOf(n)) : String.valueOf(n);
    }

    private static String formatDate(long date, String format) {
        if (date == 0) {
            return "";
        }
        SimpleDateFormat temp = new SimpleDateFormat(format);
        return temp.format(new Date(date));
    }

    public static String formatDate(long date) {
        return formatDate(date, "dd-MMM-yyyy HH:mm:ss");
    }

    public static String formatDateShort(long date) {
        return formatDate(date, "MMM dd, HH:mm");
    }

    public static String formatDateNum(long date) {
        return formatDate(date, "yyyy-MM-dd HH:mm:ss");
    }

    //
    // These methods will be exposed in the plugin API.
    //

    public static String formatCustomDateOnly(long date) {
        if (date == 0) {
            return "";
        }
        return formatDate(date, "dd-MMM-yyyy");
    }

    public static String formatCustomTimeOnly(long date) {
        return formatCustomTimeOnly(date, true);
    }

    public static String formatCustomTimeOnly(long date, boolean with_secs) {
        if (date == 0) {
            return "";
        }
        return formatDate(date, (with_secs) ? "HH:mm:ss" : "HH:mm");
    }

    public static String formatCustomDateTime(long date) {
        if (date == 0) {
            return "";
        }
        return formatDate(date);
    }

    //
    // End methods
    //

    public static String formatTime(long time) {
        return (TimeFormatter.formatColon(time / 1000));
    }

    /**
     * Format a real number to the precision specified. Does not round the number or truncate trailing zeros.
     * 
     * @param value
     *            real number to format
     * @param precision
     *            # of digits after the decimal place
     * @return formatted string
     */
    public static String formatDecimal(double value, int precision) {
        return formatDecimal(value, precision, TRUNCZEROS_NO, ROUND_NO);
    }

    /**
     * Format a real number
     * 
     * @param value
     *            real number to format
     * @param precision
     *            max # of digits after the decimal place
     * @param bTruncateZeros
     *            remove any trailing zeros after decimal place
     * @param bRound
     *            Whether the number will be rounded to the precision, or truncated off.
     * @return formatted string
     */
    public static String formatDecimal(double value, int precision, boolean bTruncateZeros, boolean bRound) {
        if (Double.isNaN(value) || Double.isInfinite(value)) {
            return Constants.INFINITY_STRING;
        }

        double tValue;
        if (bRound) {
            tValue = value;
        } else {
            // NumberFormat rounds, so truncate at precision
            if (precision == 0) {
                tValue = (long) value;
            } else {
                double shift = Math.pow(10, precision);
                tValue = ((long) (value * shift)) / shift;
            }
        }

        int cache_index = (precision << 2) + ((bTruncateZeros ? 1 : 0) << 1) + (bRound ? 1 : 0);

        NumberFormat nf = null;

        if (cache_index < cached_number_formats.length) {
            nf = cached_number_formats[cache_index];
        }

        if (nf == null) {
            nf = NumberFormat.getNumberInstance();
            nf.setGroupingUsed(false); // no commas
            if (!bTruncateZeros) {
                nf.setMinimumFractionDigits(precision);
            }
            if (bRound) {
                nf.setMaximumFractionDigits(precision);
            }

            if (cache_index < cached_number_formats.length) {
                cached_number_formats[cache_index] = nf;
            }
        }

        return nf.format(tValue);
    }

    /**
     * Attempts vaguely smart string truncation by searching for largest token and truncating that
     * 
     * @param str
     * @param width
     * @return
     */

    public static String truncateString(String str, int width) {
        int excess = str.length() - width;

        if (excess <= 0) {

            return (str);
        }

        excess += 3; // for ...

        int token_start = -1;
        int max_len = 0;
        int max_start = 0;

        for (int i = 0; i < str.length(); i++) {

            char c = str.charAt(i);

            if (Character.isLetterOrDigit(c) || c == '-' || c == '~') {

                if (token_start == -1) {

                    token_start = i;

                } else {

                    int len = i - token_start;

                    if (len > max_len) {

                        max_len = len;
                        max_start = token_start;
                    }
                }
            } else {

                token_start = -1;
            }
        }

        if (max_len >= excess) {

            int trim_point = max_start + max_len;

            return (str.substring(0, trim_point - excess) + "..." + str.substring(trim_point));
        } else {

            return (str.substring(0, width - 3) + "...");
        }
    }

    // Used to test fractions and displayformatter.
    // Keep until everything works okay.
    public static void main(String[] args) {
        // set decimal display to ","
        // Locale.setDefault(Locale.GERMAN);

        double d = 0.000003991630774821635;
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumFractionDigits(6);
        nf.setMinimumFractionDigits(6);
        String s = nf.format(d);

        System.out.println("Actual: " + d); // Displays 3.991630774821635E-6
        System.out.println("NF/6:   " + s); // Displays 0.000004
        // should display 0.000003
        System.out.println("DF:     " + DisplayFormatters.formatDecimal(d, 6));
        // should display 0
        System.out.println("DF 0:   " + DisplayFormatters.formatDecimal(d, 0));
        // should display 0.000000
        System.out.println("0.000000:" + DisplayFormatters.formatDecimal(0, 6));
        // should display 0.001
        System.out.println("0.001:" + DisplayFormatters.formatDecimal(0.001, 6, TRUNCZEROS_YES, ROUND_NO));
        // should display 0
        System.out.println("0:" + DisplayFormatters.formatDecimal(0, 0));
        // should display 123456
        System.out.println("123456:" + DisplayFormatters.formatDecimal(123456, 0));
        // should display 123456
        System.out.println("123456:" + DisplayFormatters.formatDecimal(123456.999, 0));
        System.out.println(DisplayFormatters.formatDecimal(0.0 / 0, 3));
    }

    public static char getDecimalSeparator() {
        return decimalSeparator;
    }
}